use std::{collections::HashMap, path::PathBuf, time::Duration};

use axum::{
  Router,
  body::Body,
  http::StatusCode,
  response::{Html, IntoResponse},
  routing::get,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Quality {
  Low,    // 360p
  Medium, // 720p
  High,   // 1080p
  Ultra,  // 2160p
}

impl Quality {
  fn params(&self) -> (String, String, String) {
    match self {
      Quality::Low => ("480x360".into(), "500k".into(), "64k".into()),
      Quality::Medium => ("1280x720".into(), "1500k".into(), "128k".into()),
      Quality::High => ("1920x1080".into(), "3000k".into(), "192k".into()),
      Quality::Ultra => ("3840x2160".into(), "8000k".into(), "384k".into()),
    }
  }

  fn resolution(&self) -> (u32, u32) {
    match self {
      Quality::Low => (480, 360),
      Quality::Medium => (1280, 720),
      Quality::High => (1920, 1080),
      Quality::Ultra => (3840, 2160),
    }
  }

  fn dir_name(&self) -> &'static str {
    match self {
      Quality::Low => "360p",
      Quality::Medium => "720p",
      Quality::High => "1080p",
      Quality::Ultra => "2160p",
    }
  }

  fn from_dir_name(name: &str) -> Option<Self> {
    match name {
      "360p" => Some(Self::Low),
      "720p" => Some(Self::Medium),
      "1080p" => Some(Self::High),
      "2160p" => Some(Self::Ultra),
      _ => None,
    }
  }

  fn all_sorted() -> Vec<Self> {
    vec![Self::Low, Self::Medium, Self::High, Self::Ultra]
  }
}

#[derive(Debug)]
struct SourceInfo {
  width: u32,
  height: u32,
  bitrate: u32, // kbps
}

#[derive(Clone)]
struct AppState {
  allowed_files: std::sync::Arc<tokio::sync::Mutex<HashMap<String, PathBuf>>>,
  cache_root: PathBuf,
  cache_ttl: Duration,
}

async fn get_source_info(
  source_path: &std::path::Path,
) -> Result<SourceInfo, String> {
  let output = tokio::process::Command::new("ffprobe")
    .arg("-i")
    .arg(source_path.to_string_lossy().to_string())
    .arg("-v")
    .arg("error")
    .arg("-select_streams")
    .arg("v:0")
    .arg("-show_entries")
    .arg("stream=width,height,bit_rate")
    .arg("-of")
    .arg("default=noprint_wrappers=1:nokey=1")
    .stdout(std::process::Stdio::piped())
    .stderr(std::process::Stdio::piped())
    .output()
    .await
    .map_err(|e| format!("ffprobe执行失败: {}", e))?;

  if !output.status.success() {
    let err = String::from_utf8_lossy(&output.stderr);
    return Err(format!("解析视频信息失败: {}", err));
  }

  let output_str = String::from_utf8_lossy(&output.stdout);
  let mut parts = output_str.lines().filter_map(|s| s.trim().parse().ok());

  let width = parts.next().ok_or("无法解析宽度")?;
  let height = parts.next().ok_or("无法解析高度")?;
  let bitrate_bps = parts.next().ok_or("无法解析码率")?;
  let bitrate_kbps = bitrate_bps / 1000;

  Ok(SourceInfo {
    width,
    height,
    bitrate: bitrate_kbps,
  })
}

fn get_supported_qualities(source: &SourceInfo) -> Vec<Quality> {
  Quality::all_sorted()
    .into_iter()
    .filter(|q| {
      let (q_width, q_height) = q.resolution();
      q_width <= source.width && q_height <= source.height
    })
    .collect()
}

// 缓存相关函数
fn video_cache_dir(state: &AppState, video_id: &str) -> PathBuf {
  state.cache_root.join(video_id)
}

fn quality_cache_dir(
  state: &AppState,
  video_id: &str,
  quality: Quality,
) -> PathBuf {
  video_cache_dir(state, video_id).join(quality.dir_name())
}

async fn quality_cache_exists(
  state: &AppState,
  video_id: &str,
  quality: Quality,
) -> bool {
  let dir = quality_cache_dir(state, video_id, quality);
  dir.join("stream.m3u8").exists()
}

async fn generate_quality_cache(
  source_path: &std::path::Path,
  cache_dir: &std::path::Path,
  quality: Quality,
) -> Result<(), String> {
  tokio::fs::create_dir_all(cache_dir)
    .await
    .map_err(|e| format!("创建缓存目录失败: {}", e))?;

  let (resolution, video_bitrate, audio_bitrate) = quality.params();

  let status = tokio::process::Command::new("ffmpeg")
    .arg("-i")
    .arg(source_path.to_string_lossy().to_string())
    .arg("-profile:v")
    .arg("baseline")
    .arg("-level")
    .arg("4.0")
    .arg("-s")
    .arg(resolution)
    .arg("-b:v")
    .arg(video_bitrate)
    .arg("-b:a")
    .arg(audio_bitrate)
    .arg("-hls_time")
    .arg("6")
    .arg("-hls_list_size")
    .arg("0")
    .arg("-hls_segment_filename")
    .arg(
      cache_dir
        .join(format!("segment_{}%03d.ts", std::process::id()))
        .to_string_lossy()
        .to_string(),
    )
    .arg("-f")
    .arg("hls")
    .arg(cache_dir.join("stream.m3u8").to_string_lossy().to_string())
    .status()
    .await
    .map_err(|e| format!("FFmpeg启动失败: {}", e))?;

  if !status.success() {
    return Err(format!("FFmpeg转码失败（{}）", quality.dir_name()));
  }
  Ok(())
}

async fn generate_master_playlist(
  state: &AppState,
  video_id: &str,
  supported_qualities: &[Quality],
) -> Result<String, String> {
  let mut master = "#EXTM3U\n".to_string();
  master.push_str("#EXT-X-VERSION:3\n");

  for quality in supported_qualities {
    let (resolution, video_bitrate, _) = quality.params();
    let video_bitrate_kbps: u32 = video_bitrate
      .trim_end_matches('k')
      .parse()
      .map_err(|_| "解析码率失败")?;
    let total_bitrate = video_bitrate_kbps + 128;

    master.push_str(&format!(
      "#EXT-X-STREAM-INF:BANDWIDTH={},RESOLUTION={}\n",
      total_bitrate * 1000,
      resolution
    ));
    master.push_str(&format!("{}/{}\n", quality.dir_name(), "stream.m3u8"));
  }

  let master_path = video_cache_dir(state, video_id).join("master.m3u8");
  tokio::fs::write(&master_path, &master)
    .await
    .map_err(|e| format!("写入主索引文件失败: {}", e))?;

  Ok(master)
}

// 缓存清理
async fn init_cache_cleaner(cache_root: PathBuf, ttl: Duration) {
  tokio::spawn(async move {
    let mut interval = tokio::time::interval(Duration::from_secs(3600));
    loop {
      interval.tick().await;
      clean_expired_cache(&cache_root, ttl).await;
    }
  });
}

async fn clean_expired_cache(cache_root: &std::path::Path, ttl: Duration) {
  let now = std::time::SystemTime::now();
  if let Ok(entries) = tokio::fs::read_dir(cache_root).await {
    let mut dirs = Vec::new();
    let mut entries = entries;
    while let Some(entry) = entries.next_entry().await.unwrap_or(None) {
      if entry.path().is_dir() {
        dirs.push(entry);
      }
    }

    for dir in dirs {
      if let Ok(meta) = dir.metadata().await {
        if let Ok(modified) = meta.modified() {
          if now.duration_since(modified).unwrap_or(Duration::MAX) > ttl {
            let _ = tokio::fs::remove_dir_all(dir.path()).await;
            println!("清理过期缓存: {:?}", dir.path());
          }
        }
      }
    }
  }
}

async fn index() -> Html<String> {
  Html(r#"
    <!DOCTYPE html>
       <html lang="zh-CN">
       <head>
            <title>动态码率视频播放前端m3u8格式视频</title>

            <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet" />
            <script src="https://vjs.zencdn.net/7.4.1/video.js"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script>

            <meta charset="UTF-8" />
            <style>
                  .quality-selector {
                    margin: 10px 0;
                    padding: 8px;
                  }
            </style>
      </head>

      <body>
        <h1>动态码率视频播放器</h1>
        <div>
          <label>选择码率：</label>
          <select class="quality-selector" id="qualitySelect">
            <option value="auto">自动切换（默认）</option>
            <option value="360p">360p（流畅）</option>
            <option value="720p">720p（高清）</option>
            <option value="1080p">1080p（超清）</option>
            <option value="2160p">2160p（4K）</option>
          </select>
        </div>
        <video
          id="videoPlayer"
          class="video-js vjs-default-skin vjs-big-play-centered"
          controls
          preload="auto"
          width="640"
          height="268"
          data-setup="{}"
          width="1280"
          controls
          autoplay
        >
          <source src="/stream/test/master.m3u8" type="application/x-mpegURL" />
          你的浏览器不支持HLS流媒体
        </video>

        <script>
          const select = document.getElementById("qualitySelect");
          const video = document.getElementById("videoPlayer");
          const baseUrl = "/stream/test/";

          select.addEventListener("change", () => {
            const quality = select.value;
            let src;
            if (quality === "auto") {
              src = baseUrl + "master.m3u8";
            } else {
              src = baseUrl + quality + "/stream.m3u8";
            }
            const myVideo = videojs("videoPlayer", {
              bigPlayButton: true,
              textTrackDisplay: false,
              posterImage: false,
              errorDisplay: false,
            });

            myVideo.src({ src });
            myVideo.load();
            myVideo.play();
          });
        </script>
      </body>
    </html>
    "#.to_string())
}

// 主索引文件路由（严格匹配 master.m3u8）
async fn master_playlist(
  axum::extract::Path(video_id): axum::extract::Path<String>,
  axum::extract::State(state): axum::extract::State<AppState>,
) -> impl IntoResponse {
  let allowed_files = state.allowed_files.lock().await;
  let Some(source_path) = allowed_files.get(&video_id).cloned() else {
    return Err((StatusCode::NOT_FOUND, "视频不存在"));
  };
  drop(allowed_files);

  let source_info = get_source_info(&source_path)
    .await
    .map_err(|e| {
      (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("解析源视频失败: {}", e),
      )
    })
    .unwrap();
  let supported_qualities = get_supported_qualities(&source_info);
  if supported_qualities.is_empty() {
    return Err((StatusCode::INTERNAL_SERVER_ERROR, "无可用码率配置"));
  }

  let video_cache = video_cache_dir(&state, &video_id);
  let master_path = video_cache.join("master.m3u8");

  if !master_path.exists() {
    println!(
      "为 {} 生成动态码率缓存: {:?}",
      video_id, supported_qualities
    );
    for quality in &supported_qualities {
      let quality_cache = quality_cache_dir(&state, &video_id, *quality);
      if !quality_cache_exists(&state, &video_id, *quality).await {
        if let Err(_e) =
          generate_quality_cache(&source_path, &quality_cache, *quality).await
        {
          return Err((
            StatusCode::INTERNAL_SERVER_ERROR,
            "查找缓存或者生成失败",
          ));
        }
      }
    }
    generate_master_playlist(&state, &video_id, &supported_qualities)
      .await
      .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e))
      .unwrap();
  }

  let content = tokio::fs::read_to_string(&master_path)
    .await
    .map_err(|e| {
      (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("读取主索引失败: {}", e),
      )
    })
    .unwrap();

  Ok((
    [(axum::http::header::CONTENT_TYPE, "application/x-mpegURL")],
    content,
  ))
}

// 码率索引文件路由（严格匹配 stream.m3u8）
async fn quality_playlist(
  axum::extract::Path((video_id, quality_dir)): axum::extract::Path<(
    String,
    String,
  )>,
  axum::extract::State(state): axum::extract::State<AppState>,
) -> impl IntoResponse {
  // 验证路径格式：必须以 stream.m3u8 结尾
  let playlist = "stream.m3u8";

  let Some(quality) = Quality::from_dir_name(&quality_dir) else {
    return Err((StatusCode::NOT_FOUND, "不支持的码率"));
  };

  let allowed_files = state.allowed_files.lock().await;
  let Some(source_path) = allowed_files.get(&video_id).cloned() else {
    return Err((StatusCode::NOT_FOUND, "视频不存在"));
  };
  drop(allowed_files);

  let source_info = match get_source_info(&source_path).await {
    Ok(info) => info,
    Err(_e) => {
      return Err((StatusCode::INTERNAL_SERVER_ERROR, "获取视频信息失败"));
    }
  };
  let supported_qualities = get_supported_qualities(&source_info);
  if !supported_qualities.contains(&quality) {
    return Err((StatusCode::NOT_FOUND, "该码率不被源视频支持"));
  }

  let quality_cache = quality_cache_dir(&state, &video_id, quality);
  if !quality_cache_exists(&state, &video_id, quality).await {
    if let Err(_e) =
      generate_quality_cache(&source_path, &quality_cache, quality).await
    {
      return Err((
        StatusCode::INTERNAL_SERVER_ERROR,
        "获取对应码率的视频或生成视频失败",
      ));
    }
  }

  let playlist_path = quality_cache.join(playlist);
  let content = tokio::fs::read_to_string(&playlist_path)
    .await
    .map_err(|e| {
      (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("读取索引失败: {}", e),
      )
    })
    .unwrap();
  let content = content.replace(
    "segment_",
    &format!("/stream/{}/{}/segment_", video_id, quality_dir),
  );

  Ok((
    [(axum::http::header::CONTENT_TYPE, "application/x-mpegURL")],
    content,
  ))
}

// 分片文件路由（严格匹配 segment_xxx.ts 格式）
async fn quality_segment(
  axum::extract::Path((video_id, quality_dir, segment)): axum::extract::Path<(
    String,
    String,
    String,
  )>,
  axum::extract::State(state): axum::extract::State<AppState>,
) -> impl IntoResponse {
  // 验证分片文件名格式（必须以 segment_ 开头且以 .ts 结尾）
  if !segment.starts_with("segment_") || !segment.ends_with(".ts") {
    return Err((StatusCode::BAD_REQUEST, "无效的分片文件名"));
  }

  let Some(quality) = Quality::from_dir_name(&quality_dir) else {
    return Err((StatusCode::NOT_FOUND, "不支持的码率"));
  };

  let allowed_files = state.allowed_files.lock().await;
  if !allowed_files.contains_key(&video_id) {
    return Err((StatusCode::NOT_FOUND, "视频不存在"));
  }
  drop(allowed_files);

  let quality_cache = quality_cache_dir(&state, &video_id, quality);
  let segment_path = quality_cache.join(&segment);
  if !segment_path.exists() {
    return Err((StatusCode::NOT_FOUND, "分片文件不存在"));
  }

  // 以流的方式读取文件（适合大文件）
  let file = tokio::fs::File::open(&segment_path)
    .await
    .map_err(|e| {
      (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("打开分片失败: {}", e),
      )
        .into_response()
    })
    .unwrap();
  let bytes_stream = tokio_util::io::ReaderStream::new(file);

  Ok((
    [(axum::http::header::CONTENT_TYPE, "video/MP2T")],
    Body::from_stream(bytes_stream),
  ))
}

// ------------------------------
// 主函数（路由注册优化）
// ------------------------------

#[tokio::main]
async fn main() {
  let cache_root = PathBuf::from("./dynamic_quality_cache");
  let cache_ttl = Duration::from_secs(86400);

  tokio::fs::create_dir_all(&cache_root)
    .await
    .unwrap_or_default();
  init_cache_cleaner(cache_root.clone(), cache_ttl).await;

  let mut allowed_files = HashMap::new();
  allowed_files.insert(
    "test".to_string(),
    PathBuf::from("test.mp4"), // 替换为你的源视频
  );

  let state = AppState {
    allowed_files: std::sync::Arc::new(tokio::sync::Mutex::new(allowed_files)),
    cache_root: cache_root.clone(),
    cache_ttl,
  };

  // ------------------------------
  // 路由优化核心：
  // 1. 静态路径优先于参数路径
  // 2. 明确文件类型的路由规则（xxx.m3u8 / segment_xxx.ts）
  // 3. 减少参数路由的模糊性
  // ------------------------------
  let app = Router::new()
    .route("/", get(index))
    // 1. 主索引文件（最高优先级，完全静态路径）
    .route("/stream/{video_id}/master.m3u8", get(master_playlist))
    // 2. 码率索引文件（次高优先级，固定文件名 stream.m3u8）
    .route(
      "/stream/{video_id}/{quality_dir}/stream.m3u8",
      get(quality_playlist),
    )
    // 3. 分片文件（最低优先级，匹配 segment_xxx.ts 格式）
    .route(
      "/stream/{video_id}/{quality_dir}/{segment}",
      get(quality_segment),
    )
    .with_state(state);

  let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
    .await
    .unwrap();
  println!("服务器启动：http://{}", listener.local_addr().unwrap());
  axum::serve(listener, app).await.unwrap();
}
